﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.Caching;
using System.Xml.Linq;

using ProjectBase.Core;
using ProjectBase.Core.Settings;
using ProjectBase.Data.Xml;

using Survey.Common;

namespace Survey.Data.Providers
{
    /// <summary>
    ///     This class reads the "Project.config" file and converts it
    ///     into the IBaseSettings object. If any bugs or incorrectness appears in the .config file
    ///     default BaseSettings object values are used
    /// </summary>
    public class ProjectSettingsProvider : XmlDao, IBaseSettingsProvider, IProjectSettingsProvider
    {
        #region members
        class Const
        {
            // GLOBAL
            public const string Action = "action";
            public const string Add = "add";
            public const string AppDataFolder = @"App_Data\";
            public const string ApplicationRoleName = "applicationRoleName";
            public const string ConfigurationFile = "configurationFile";
            public const string TypesToSkip = "typesToSkip";
            public const string ConnectionStringNameAdoNet = "connectionStringNameAdoNet";
            public const string Controller = "controller";
            public const string MvcAssemblySuffix = "Mvc";
            public const string ControllerSuffix = "Controller";
            public const string DaoSession = "daoSession";
            public const string DataConfig = "dataConfig";
            public const string DataSourceName = "/Config/Project.config";
            public const string DefaultListRowCount = "defaultListRowCount";

            public const string EnableDb = "enableDb";
            public const string EnableNh = "enableNHibernate";
            public const string EnableSessionKeeper = "enableSessionKeeper";
            public const string EnableXml = "enableXml";
            public const string Exclude = "exclude";
            public const string FactoryProviderType = "factoryProviderType";

            public const string InterfaceController = "IController";
            public const string Key = "key";
            public const string MenuConfig = "menuConfig";
            public const string Name = "name";
            public const string Path = "path";
            public const string ElementType = "type";
            public const string PrefixForNotFoundTranslation = "prefixForNotFoundTranslation";
            public const string ProjectElement = "project";
            public const string Role = "role";
            public const string RoleProviderName = "roleProviderName";
            public const string RolesConvertor = "rolesConvertor";
            public const string SettingsElement = "settings";
            public const string UseCurrentRole = "useCurrentRole";
            public const string FileCacheDbPath = "fileCacheDbPath";
            public const string FileCacheStaticPath = "fileCacheStaticPath";
            public const string AllowFileCacheDependencyForStatic = "allowFileCacheDependencyForStatic";
            public const string DaoDefaultLikeCollation = "daoDefaultLikeCollation";
            public const string ValidateRequestOnController = "validateRequestOnController";
            public const string Value = "value";
            public const string WriteActionList = "writeActionList";
            public static readonly XNamespace Ns = "http://ProjectBase/Config.xsd";
            Const() { }
        }
        #endregion members

        #region public IBaseSettings
        protected virtual ProjectSettings Settings
        {
            get
            {
                var settings = this.Cache()[DataSourceName] as ProjectSettings;
                if (settings.IsNull()
                    &&
                    DataSource.Is())
                {
                    settings = new ProjectSettings();
                    if (DataSource.Is())
                    {
                        SetProjectSettings(settings);

                        SetDefaultListRowCount(settings);
                        SetEnableSessionKeeper(settings);
                        SetUseCurrentRole(settings);
                        SetPrefixForNotFoundTranslation(settings);
                        SetValidateRequestOnController(settings);
                        SetConnectionStringNameAdoNet(settings);
                        SetCacheDependencyFiles(settings);
                        SetDaoDefaultLikeCollation(settings);
                        FillRolesConvertor(settings);
                        FillWriteActionList(settings);
                        FillDaoSessions(settings);
                        SetfactoryProviderType(settings);
                        SetMenuFilePath(settings);
                        SetDataFilePath(settings);
                    }
                    using (var cacheDependency = new CacheDependency(DataSourcePath))
                    {
                        this.Cache().Insert(DataSourceName, settings, cacheDependency);
                    }
                }
                return settings;
            }
        }

        #region IBaseSettingsProvider Members
        /// <summary>
        ///     The project dependent settings from the Project.config
        /// </summary>
        public virtual IBaseSettings BaseSettings
        {
            get { return Settings; }
        }
        #endregion

        #region IProjectSettingsProvider Members
        public virtual IProjectSettings ProjectSettings
        {
            get { return Settings; }
        }
        #endregion

        #endregion public IBaseSettings

        #region public Create path
        /// <summary>
        ///     Allows to access any relative path which is combined with this application absolute path.
        ///     Useful to find Config files, Priceless to be overrided for testing or batch projects
        /// </summary>
        /// <param name = "path"></param>
        /// <returns></returns>
        public virtual string CreateAbsolutePath(string path)
        {
            var target = path.Replace('/', '\\');
            if (target.StartsWith("\\", StringComparison.Ordinal))
            {
                target = target.Remove(0, 1);
            }
            return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, target);
        }

        /// <summary>
        ///     Creates the full path to the 'App_Data' folder. This is the default expected implementation.
        ///     For other purposes (Batch, Tests) target more suitable folder in your implementing IBaseSettingsProvider
        /// </summary>
        /// <param name = "fileName">file name like "file.xml" (with extension)</param>
        /// <returns>The full path to the App_Data folder file</returns>
        public virtual string CreateAppDataPath(string fileName)
        {
            var target = Const.AppDataFolder + fileName;
            return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, target);
        }

        public virtual string DefaultFilePath(string fileNameOrPath)
        {
            if (!fileNameOrPath.Contains('\\')
                && !fileNameOrPath.Contains('/'))
            {
                return CreateAppDataPath(fileNameOrPath);
            }
            return Path.GetFullPath(fileNameOrPath);
        }
        #endregion public Create path

        #region public new IDbCommand
        /// <summary>
        ///     The ProjectCore.Data layer can be used for direct ADO.NET handling.
        ///     For that purposes there is 'Db' namespace with 'DbSession' which takes care about
        ///     Request based Connection opening, closing and transaction management.
        ///     Your project based IBaseSettingsProvider simply must provide the correct type (Sql, Oracle etc.)
        ///
        ///     TODO move the dbCommand and dbConnection types to Project.config - if needed
        /// </summary>
        /// <returns>New IDbCommand instance, which is used on ProjectCore.Data layer for DbSession pruproses</returns>
        public virtual IDbCommand NewDbCommand()
        {
            var providerName = BaseSettings.DefaultConnectionStringSettingsAdoNet.ProviderName;
            var connectionString = BaseSettings.DefaultConnectionStringSettingsAdoNet.ConnectionString;

            var factory = System.Data.Common.DbProviderFactories.GetFactory(providerName);
            var con = factory.CreateConnection();
            con.ConnectionString = connectionString;

            var command = factory.CreateCommand();
            command.Connection = con;

            return command;
        }
        #endregion public new IDbCommand

        #region Global - Project.config reader

        #region settings
        protected virtual void SetDefaultListRowCount(BaseSettings settings)
        {
            short value;
            if (short.TryParse(GetSettingsElementValue(Const.DefaultListRowCount), out value))
            {
                settings.DefaultListRowCount = value;
            }
        }

        protected virtual void SetEnableSessionKeeper(BaseSettings settings)
        {
            bool value;
            if (bool.TryParse(GetSettingsElementValue(Const.EnableSessionKeeper), out value))
            {
                settings.EnableSessionKeeper = value;
            }
        }

        protected virtual void SetPrefixForNotFoundTranslation(BaseSettings settings)
        {
            var value = GetSettingsElementValue(Const.PrefixForNotFoundTranslation);
            if (value.Is())
            {
                settings.PrefixForNotFoundTranslation = value;
            }
        }

        protected virtual void SetValidateRequestOnController(BaseSettings settings)
        {
            bool value;
            if (bool.TryParse(GetSettingsElementValue(Const.ValidateRequestOnController), out value))
            {
                settings.ValidateRequestOnController = value;
            }
        }

        protected virtual void SetfactoryProviderType(BaseSettings settings)
        {
            var value = GetSettingsElementValue(Const.FactoryProviderType);
            if (value.IsNotEmpty())
            {
                var type = Type.GetType(value);
                if (type.Is())
                {
                    settings.FactoryProviderType = type;
                }
            }
        }

        protected virtual void SetConnectionStringNameAdoNet(BaseSettings settings)
        {
            var value = GetSettingsElementValue(Const.ConnectionStringNameAdoNet);
            if (value.IsNotEmpty())
            {
                settings.ConnectionStringNameAdoNet = value;
            }
        }

        protected virtual void SetUseCurrentRole(BaseSettings settings)
        {
            bool value;
            if (bool.TryParse(GetSettingsElementValue(Const.UseCurrentRole), out value))
            {
                settings.UseCurrentRole = value;
            }
        }

        protected virtual void SetCacheDependencyFiles(BaseSettings settings)
        {
            var fileDb = GetSettingsElementValue(Const.FileCacheDbPath);
            if (fileDb.IsNotEmpty())
            {
                settings.FileCacheDbPath = fileDb;
            }
            var fileStatic = GetSettingsElementValue(Const.FileCacheStaticPath);
            if (fileStatic.IsNotEmpty())
            {
                settings.FileCacheStaticPath = fileStatic;
            }
            bool value;
            if (bool.TryParse(GetSettingsElementValue(Const.AllowFileCacheDependencyForStatic), out value))
            {
                settings.AllowFileCacheDependencyForStatic = value;
            }
        }
        protected virtual void SetDaoDefaultLikeCollation(BaseSettings settings)
        {
            var collation = GetSettingsElementValue(Const.DaoDefaultLikeCollation);
            if(collation.IsNotEmpty())
            {
                settings.DaoDefaultLikeCollation = collation;
            }
        }
        #endregion settings

        #region config files
        protected virtual void SetMenuFilePath(IBaseSettings settings)
        {
            var value = GetConfigPathElementValue(Const.MenuConfig);
            if (value.IsNotEmpty())
            {
                settings.ConfigurationFilePath.Menu = value;
            }
        }

        protected virtual void SetDataFilePath(IBaseSettings settings)
        {
            var files = GetConfigPathElementValues(Const.DataConfig);
            if (files.IsNotEmpty())
            {
                settings.ConfigurationFilePath.NHibernateFiles = files;
            }
        }
        #endregion config files

        #region collections
        /// <summary>
        ///     Loads the external role mapping for Default ApplicationRoleProvider
        /// </summary>
        /// <param name = "settings"></param>
        protected virtual void FillRolesConvertor(BaseSettings settings)
        {
            settings.RolesConvertor = new Dictionary<string, string>();
            var elements = DataSource.Root.Element(Const.Ns + Const.RolesConvertor);
            if (elements.Is())
            {
                var roles = elements.Elements(Const.Ns + Const.Role);
                if (roles.Is()
                    &&
                    roles.Count() > 0)
                {
                    foreach (var role in roles)
                    {
                        var key = role.Attribute(Const.RoleProviderName);
                        var value = role.Attribute(Const.ApplicationRoleName);
                        if (key.Is() && key.Value.IsNotEmpty()
                            && value.Is()
                            && value.Value.IsNotEmpty())
                        {
                            settings.RolesConvertor[key.Value] = value.Value;
                        }
                    }
                }
            }
        }

        /// <summary>
        ///     Reads the 'rolesConvertor' element and fills the list of write actions
        /// </summary>
        /// <param name = "settings"></param>
        protected virtual void FillWriteActionList(BaseSettings settings)
        {
            settings.WriteActions = AppendProjectWriteActions(LoadTransactionActions());
        }
        #endregion collections

        #region dao sessions
        protected virtual void FillDaoSessions(BaseSettings settings)
        {
            bool value;
            if (bool.TryParse(GetDaoSessionElementValue(Const.EnableDb), out value))
            {
                settings.EnableDbDao = value;
            }
            if (bool.TryParse(GetDaoSessionElementValue(Const.EnableNh), out value))
            {
                settings.EnableNHDao = value;
            }
            if (bool.TryParse(GetDaoSessionElementValue(Const.EnableXml), out value))
            {
                settings.EnableXmlDao = value;
            }
        }
        #endregion dao sessions

        #region xml
        /// <summary>
        ///     Searches the root element for the 'project' child.
        ///     Expects that there will be 'add' elements with attributes 'key' 'value'.
        ///     Returned value is the 'value' of the 'add' element with the 'key' equal to provided parameter
        /// </summary>
        /// <param name = "key"></param>
        /// <returns>empty if no match is found</returns>
        protected virtual string GetProjectElementValue(string key)
        {
            var project = DataSource.Root.Element(Const.Ns + Const.ProjectElement);
            if (project.Is())
            {
                var element = project.Elements(Const.Ns + Const.Add)
                    .Where(e => e.Attribute(Const.Key).Is()
                                && e.Attribute(Const.Key).Value.IsEqual(key))
                    .FirstOrDefault();
                if (element.Is())
                {
                    var attribute = element.Attribute(Const.Value);
                    if (attribute.Is())
                    {
                        return attribute.Value;
                    }
                }
            }
            return string.Empty;
        }

        /// <summary>
        ///     Searches the root element for the 'settings' child - and its XElement with provided name and Ns.
        ///     If there is one with attribute 'Value' its value is returned.
        ///     Else string.Empty.
        /// </summary>
        /// <param name = "elementName"></param>
        /// <returns></returns>
        protected virtual string GetSettingsElementValue(string elementName)
        {
            var element = GetElement(Const.SettingsElement, elementName);
            if (element.Is())
            {
                var attribute = element.Attribute(Const.Value);
                if (attribute.Is())
                {
                    return attribute.Value;
                }
            }
            return string.Empty;
        }

        /// <summary>
        ///     returns the value of the path attribute of provided element
        /// </summary>
        /// <param name = "elementName"></param>
        /// <returns></returns>
        protected virtual string GetConfigPathElementValue(string elementName)
        {
            var element = GetElement(Const.ConfigurationFile, elementName);
            if (element.Is())
            {
                var attribute = element.Attribute(Const.Path);
                if (attribute.Is())
                {
                    return attribute.Value;
                }
            }
            return string.Empty;
        }

        protected virtual IEnumerable<string> GetConfigPathElementValues(string elementName)
        {
            var list = new List<string>();

            var parent = DataSource.Root.Element(Const.Ns + Const.ConfigurationFile);
            if (parent.IsNull())
            {
                return list;
            }

            var elements = parent.Elements(Const.Ns + elementName);

            foreach (var filePath in elements)
            {
                var attribute = filePath.Attribute(Const.Path);
                if (attribute.Is())
                {
                    list.Add(attribute.Value);
                }
            }
            return list;
        }

        /// <summary>
        ///     Searches the root element for the 'daoSession' child - and its XElement with provided name and Ns.
        ///     If there is one with attribute 'Value' its value is returned.
        ///     Else string.Empty.
        /// </summary>
        /// <param name = "elementName"></param>
        /// <returns></returns>
        protected virtual string GetDaoSessionElementValue(string elementName)
        {
            var element = GetElement(Const.DaoSession, elementName);
            if (element.Is())
            {
                var attribute = element.Attribute(Const.Value);
                if (attribute.Is())
                {
                    return attribute.Value;
                }
            }
            return string.Empty;
        }

        /// <summary>
        ///     Get the element root/parentName/elementName - or null
        /// </summary>
        /// <param name = "parentName"></param>
        /// <param name = "elementName"></param>
        /// <returns></returns>
        protected virtual XElement GetElement(string parentName, string elementName)
        {
            var parent = DataSource.Root.Element(Const.Ns + parentName);
            if (parent.Is())
            {
                return parent.Element(Const.Ns + elementName);
            }
            return null;
        }
        #endregion xml

        #region Write Action list
        /// <summary>
        ///     Searches the Controller .dll for [Transaction] attribute decorated actions
        /// </summary>
        /// <returns></returns>
        protected virtual IList<WriteAction> LoadTransactionActions()
        {
            IList<WriteAction> writeActions = new List<WriteAction>();

            // I. Current assembly
            var currentAssemblyName = Assembly
                .GetAssembly(typeof(ProjectSettingsProvider))
                .FullName
                .Split(',')[0];

            // II. Convert current name to the suffix '.controllers'
            var controllersAssemblyName = currentAssemblyName
                .Replace(
                    currentAssemblyName.Split('.')[currentAssemblyName.Split('.').Length - 1] // e.g. Data
                    , Const.MvcAssemblySuffix); // into Mvc.Controllers

            // III. iterate to find out all controller classes, derived from InterfaceCoreController
            foreach (var controller in Assembly.Load(controllersAssemblyName)
                .GetTypes()
                .Where(t => !t.IsAbstract && t.GetInterface(Const.InterfaceController).Is()))
            {
                // IV. TransactionAttribute type
                var transaction = Type.GetType("ProjectBase.Mvc.TransactionAttribute, ProjectBase.Mvc");

                // V. iterate to find out all actions decorated with [Transaction] attribute
                foreach (var method in controller.GetMethods()
                    .Where(m => m.IsPublic
                                && m.GetCustomAttributes(transaction, true).Length > 0))
                {
                    // VI. insert WriteAction
                    writeActions.Add(new WriteAction
                    {
                        Action = method.Name,
                        Controller = controller.Name.Replace(Const.ControllerSuffix, string.Empty),
                    });
                }
            }

            return writeActions;
        }

        protected virtual IList<WriteAction> AppendProjectWriteActions(IList<WriteAction> writeActions)
        {
            // I. element <writeActions>
            var elements = DataSource.Root.Element(Const.Ns + Const.WriteActionList);
            if (elements.Is())
            {
                // II. elements <actions ...>
                var actions = elements.Elements(Const.Ns + Const.Action);
                if (actions.Is()
                  && actions.Count() > 0)
                {
                    foreach (var action in actions)
                    {
                        var name = action.Attribute(Const.Name);
                        var controller = action.Attribute(Const.Controller);
                        var controllerName = controller.Is()
                                                 ? controller.Value
                                                 : null;
                        var exclude = action.Attribute(Const.Exclude).Is()
                                   && action.Attribute(Const.Exclude).Value.Equals("true",
                                          StringComparison.OrdinalIgnoreCase);

                        // III. action name exists
                        if (name.Is() && name.Value.IsNotEmpty())
                        {
                            // IV. is there already the same WriteAction in the list?
                            var existing = writeActions
                                .SingleOrDefault(wa => wa.Action.Equals(name.Value, StringComparison.Ordinal)
                                             && ((controllerName.IsEmpty() && wa.Controller.IsEmpty())
                                                 || (wa.Controller.Equals(controllerName, StringComparison.Ordinal)))
                                );
                            // V. Append missing
                            if (existing.IsNull()
                                && !exclude)
                            {
                                writeActions.Add(new WriteAction
                                {
                                    Action = name.Value,
                                    Controller = controller.Is()
                                                     ? controller.Value
                                                     : null,
                                });
                            }

                            // VI. Remove excluded WriteActions
                            if (existing.Is() && exclude)
                            {
                                writeActions.Remove(existing);
                            }
                        }
                    }
                }
                // VI. check if there is any
            }
            return writeActions;
        }
        #endregion Write Action list

        #region DataSource
        protected virtual string DataSourceName
        {
            get
            {
                var dataSource = ConfigurationManager.AppSettings["ProjectConfig"];
                return dataSource.IsNotEmpty() ? dataSource : Const.DataSourceName;
            }
        }

        protected override string DataSourcePath
        {
            get { return CreateAbsolutePath(DataSourceName); }
        }
        #endregion DataSource

        #endregion Global - Project.config reader

        #region Project - Project.config reader
        /// <summary>
        ///     Iterates through the IProjectSettings properties.
        ///     Tries to convert the project.config project/project section.
        ///     Compares the property.Name with the 'key' attribute of the 'add' element.
        ///     If match is found, value is tried to set into the Project.Settings instance.
        /// </summary>
        /// <param name = "settings"></param>
        protected virtual void SetProjectSettings(ProjectSettings settings)
        {
            foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(settings.GetType()))
            {
                var value = GetProjectElementValue(pd.Name);
                if (value.IsEmpty()) // no setting in the Project.config
                {
                    continue;
                }
                if (pd.PropertyType
                    == typeof(string)) // string property
                {
                    pd.SetValue(settings, value);
                }
                else if (pd.PropertyType.IsValueType) // int, short ...
                {
                    ValueType result;
                    if (value.IsNotEmpty()
                        && value.TryParse(CultureInfo.InvariantCulture, pd.PropertyType, out result))
                    {
                        pd.SetValue(settings, result);
                    }
                }
            }
        }
        #endregion Project - Project.config reader

        protected override bool DoSupportXInclude
        {
            get { return true; }
        }
    }
}